﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using MySql.Data.MySqlClient;
using VH6I_B4.ProductParsing;
using VH6I_B4.Datawarehouse.Models;

namespace VH6I_B4.Datawarehouse
{
    class DatawarehouseApi
    {
        private MySqlConnection _conn;
        private string _loadDateTime;
        private List<Product> _products = new List<Product>();
        private List<Supermarket> _supermarkets = new List<Supermarket>();
        private List<Timeperiod> _timeperiods = new List<Timeperiod>();
        private List<SingleSalesFact> _salesFactTmp = new List<SingleSalesFact>();
        private Dictionary<string, SalesFactRecord> _salesFacts = new Dictionary<string, SalesFactRecord>(); 

        public DatawarehouseApi(string connectionString)
        {
            // Open de verbinding met het datawarehouse
            _conn = new MySqlConnection(connectionString);
            _conn.Open();
        }

        public void SaveFact(FactType factType,
            Dictionary<ProductLevel, string> productInfo,
            string supermarketName,
            int year,
            int quarter,
            double factValue)
        {
            Product product = HandleProduct(productInfo);
            Supermarket supermarket = HandleSupermarket(supermarketName);
            Timeperiod timeperiod = HandleTimeperiod(year, quarter);
            HandleSalesFact(new SingleSalesFact(product, supermarket, timeperiod, factType, factValue));
        }

        public void Commit()
        {
            _loadDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
            CommitProducts();
            commitSupermarkets();
            commitTimeperiods();
            commitSalesFacts();
        }

        private Product HandleProduct(Dictionary<ProductLevel, string> productInfo)
        {
            Product product = new Product(productInfo);
            Product foundProduct = null;

            foreach (Product p in _products)
            {
                if (product.Equals(p))
                {
                    foundProduct = p;
                    break;
                }
            }

            if (foundProduct == null)
            {
                foundProduct = product;
                _products.Add(product);
            }

            return foundProduct;
        }

        private Supermarket HandleSupermarket(string name)
        {
            Supermarket supermarket = new Supermarket(name);
            Supermarket foundSupermarket = null;

            foreach (Supermarket s in _supermarkets)
            {
                if (supermarket.Equals(s))
                {
                    foundSupermarket = s;
                    break;
                }
            }

            if (foundSupermarket == null)
            {
                foundSupermarket = supermarket;
                _supermarkets.Add(supermarket);
            }

            return foundSupermarket;
        }

        public Timeperiod HandleTimeperiod(int year, int quarter)
        {
            Timeperiod timeperiod = new Timeperiod(year, quarter);
            Timeperiod foundTimeperiod = null;

            foreach (Timeperiod t in _timeperiods)
            {
                if (timeperiod.Equals(t))
                {
                    foundTimeperiod = t;
                    break;
                }
            }

            if (foundTimeperiod == null)
            {
                foundTimeperiod = timeperiod;
                _timeperiods.Add(timeperiod);
            }

            return foundTimeperiod;
        }

        private void HandleSalesFact(SingleSalesFact salesFact)
        {
            // Sla de fact op in een tijdelijke lijst.
            // Dit moet omdat de losse facts uit meerdere bestanden komen
            // en omdat ze moeten worden gecombineerd tot 1 record.
            _salesFactTmp.Add(salesFact);
        }

        private void CommitProducts()
        {
            // Start een insert query
            StringBuilder sqlInsert = new StringBuilder("INSERT INTO product (major_brand, apparaat, type, beleving, soort, formaat, smaak, barcode) VALUES ");
            int orgLength = sqlInsert.Length;

            foreach (Product product in _products)
            {
                // Kijk of dit product nog niet bestaat
                string sqlSelect = "SELECT COUNT(*) as 'aantal_rijen' FROM product WHERE";
                sqlSelect += " `major_brand` = '" + product.MajorBrand + "' AND";
                sqlSelect += " `apparaat` = '" + product.Apparaat + "' AND";
                sqlSelect += " `type` = '" + product.Type + "' AND";
                sqlSelect += " `beleving` = '" + product.Beleving + "' AND";
                sqlSelect += " `soort` = '" + product.Soort + "' AND";
                sqlSelect += " `formaat` = '" + product.Formaat + "' AND";
                sqlSelect += " `smaak` = '" + product.Smaak + "' AND";
                sqlSelect += " `barcode` = '" + product.Barcode + "'";

                MySqlCommand cmd = new MySqlCommand(sqlSelect, _conn);
                int numRows = Convert.ToInt16(cmd.ExecuteScalar());
                cmd.Dispose();

                // Bestaat dit product nog niet?
                if (numRows == 0)
                {
                    // Ja: Breidt het insert statement uit
                    sqlInsert.Append("('" + product.MajorBrand + "',");
                    sqlInsert.Append(" '" + product.Apparaat + "',");
                    sqlInsert.Append(" '" + product.Type + "',");
                    sqlInsert.Append(" '" + product.Beleving + "',");
                    sqlInsert.Append(" '" + product.Soort + "',");
                    sqlInsert.Append(" '" + product.Formaat + "',");
                    sqlInsert.Append(" '" + product.Smaak + "',");
                    sqlInsert.Append(" '" + product.Barcode + "'), ");
                }
            }

            // Zijn er waardes toegevoegd?
            if (orgLength < sqlInsert.Length)
            {
                // Ja: Voer de insert query uit
                MySqlCommand cmdInsert = new MySqlCommand(sqlInsert.ToString(0, sqlInsert.Length - 2), _conn);
                cmdInsert.ExecuteNonQuery();
            }
        }

        private void commitSupermarkets()
        {
            // Start een insert query
            StringBuilder sqlInsert = new StringBuilder("INSERT INTO supermarket (name) VALUES ");
            int orgLength = sqlInsert.Length;

            foreach (Supermarket supermarket in _supermarkets)
            {
                // Kijk of deze supermarkt nog niet bestaat
                string sqlSelect = "SELECT COUNT(*) as 'aantal_rijen' FROM supermarket WHERE name = '" + supermarket.Name + "'";
                MySqlCommand cmd = new MySqlCommand(sqlSelect, _conn);
                int numRows = Convert.ToInt16(cmd.ExecuteScalar());
                cmd.Dispose();

                // Bestaat deze supermarkt nog niet?
                if (numRows == 0)
                {
                    // Ja: Breidt het insert statement uit
                    sqlInsert.Append("('" + supermarket.Name + "'), ");
                }
            }

            // Zijn er waardes toegevoegd?
            if (orgLength < sqlInsert.Length)
            {
                // Ja: Voer de insert query uit
                MySqlCommand cmdInsert = new MySqlCommand(sqlInsert.ToString(0, sqlInsert.Length - 2), _conn);
                cmdInsert.ExecuteNonQuery();
            }
        }

        private void commitTimeperiods()
        {
            // Start een insert query
            StringBuilder sqlInsert = new StringBuilder("INSERT INTO timeperiod (year, quarter) VALUES ");
            int orgLength = sqlInsert.Length;

            foreach (Timeperiod timeperiod in _timeperiods)
            {
                // Kijk of deze timeperiod nog niet bestaat
                string sqlSelect = "SELECT COUNT(*) as 'aantal_rijen' FROM timeperiod WHERE";
                sqlSelect += " `year` = '" + timeperiod.Year + "' AND";
                sqlSelect += " `quarter` = '" + timeperiod.Quarter + "'";

                MySqlCommand cmd = new MySqlCommand(sqlSelect, _conn);
                int numRows = Convert.ToInt16(cmd.ExecuteScalar());
                cmd.Dispose();

                // Bestaat deze timeperiod nog niet?
                if (numRows == 0)
                {
                    // Ja: Breidt het insert statement uit
                    sqlInsert.Append("('" + timeperiod.Year + "', '" + timeperiod.Quarter + "'), ");
                }
            }

            // Zijn er waardes toegevoegd?
            if (orgLength < sqlInsert.Length)
            {
                // Ja: Voer de insert query uit
                MySqlCommand cmdInsert = new MySqlCommand(sqlInsert.ToString(0, sqlInsert.Length - 2), _conn);
                cmdInsert.ExecuteNonQuery();
            }
        }

        private void commitSalesFacts()
        {
            // De facts worden door de readers doorgestuurd zonder ID
            // terwijl die wel nodig is als vreemde sleutel.
            // Deze loop zort er voor dat ieder product, supermarkt en timeperiod
            // een ID heeft zodat deze kan worden gebruikt tijdens het inserten.
            foreach (SingleSalesFact salesFact in _salesFactTmp)
            {
                string sqlSelect;
                MySqlCommand cmd;

                // Heeft het product van deze fact geen ID
                if (salesFact.Product.Id == 0)
                {
                    // Ja: Haal de ID op uit de database
                    sqlSelect = "SELECT product_id FROM product WHERE";
                    sqlSelect += " `major_brand` = '" + salesFact.Product.MajorBrand + "' AND";
                    sqlSelect += " `apparaat` = '" + salesFact.Product.Apparaat + "' AND";
                    sqlSelect += " `type` = '" + salesFact.Product.Type + "' AND";
                    sqlSelect += " `beleving` = '" + salesFact.Product.Beleving + "' AND";
                    sqlSelect += " `soort` = '" + salesFact.Product.Soort + "' AND";
                    sqlSelect += " `formaat` = '" + salesFact.Product.Formaat + "' AND";
                    sqlSelect += " `smaak` = '" + salesFact.Product.Smaak + "' AND";
                    sqlSelect += " `barcode` = '" + salesFact.Product.Barcode + "'";

                    // Lees de resultset en stop het product id in het product object
                    cmd = new MySqlCommand(sqlSelect, _conn);
                    salesFact.Product.Id = Convert.ToInt16(cmd.ExecuteScalar());
                    cmd.Dispose();
                }

                // Heeft de supermarkt van deze fact geen ID
                if (salesFact.Supermarket.Id == 0)
                {
                    // Ja: Haal de ID op uit de database
                    sqlSelect = "SELECT supermarket_id FROM supermarket WHERE";
                    sqlSelect += " `name` = '" + salesFact.Supermarket.Name + "'";

                    // Lees de resultset en stop het supermarkt id in het supermarkt object
                    cmd = new MySqlCommand(sqlSelect, _conn);
                    salesFact.Supermarket.Id = Convert.ToInt16(cmd.ExecuteScalar());
                    cmd.Dispose();
                }

                // Heeft de timeperiod van deze fact geen ID
                if (salesFact.Timeperiod.Id == 0)
                {
                    // Ja: Haal de ID op uit de database
                    sqlSelect = "SELECT timeperiod_id FROM timeperiod WHERE";
                    sqlSelect += " `year` = '" + salesFact.Timeperiod.Year + "' AND";
                    sqlSelect += " `quarter` = '" + salesFact.Timeperiod.Quarter + "'";

                    // Lees de resultset en stop het timeperiod id in het timeperiod object
                    cmd = new MySqlCommand(sqlSelect, _conn);
                    salesFact.Timeperiod.Id = Convert.ToInt16(cmd.ExecuteScalar());
                    cmd.Dispose();
                }

                // Nu zijn de IDs bekend, maak een unieke key
                // Alle feiten worden onder deze key opgeslagen (sales, promotion, volume, etc)
                string factKey = salesFact.Product.Id + "-" + salesFact.Supermarket.Id + "-" + salesFact.Timeperiod.Id;

                // Bestaat deze key al?
                if (_salesFacts.ContainsKey(factKey))
                {
                    // Ja: Bewerk de bestaande
                    _salesFacts[factKey].SetFact(salesFact.CoffeeFactType, salesFact.FactValue);
                }
                else
                {
                    // Nee: Maak een nieuwe fact record
                    _salesFacts[factKey] = new SalesFactRecord(salesFact.Product,
                                                        salesFact.Supermarket,
                                                        salesFact.Timeperiod);
                    _salesFacts[factKey].SetFact(salesFact.CoffeeFactType, salesFact.FactValue);
                }
            }

            // Nu zijn staan de feiten netjes in het geheugen,
            // het is nu tijd om alle feiten in de database te laden.

            int counter = 0;
            string sqlInsertFactsStart = "INSERT INTO sales_fact (product_id, supermarket_id, timeperiod_id, euro_sales, " +
                "volume_sales, unit_sales, volume_any_promotion, stores_selling, weighted_distribution, load_timestamp) VALUES ";
            StringBuilder sql = new StringBuilder(sqlInsertFactsStart);

            foreach (KeyValuePair<string, SalesFactRecord> keyVal in _salesFacts)
            {
                SalesFactRecord r = keyVal.Value;

                // Voeg SQL code toe aan de string builder
                sql.Append("(" + r.Product.Id);
                sql.Append(", " + r.Supermarket.Id);
                sql.Append(", " + r.Timeperiod.Id);
                sql.Append(", " + r.GetFact(FactType.EURO_SALES).ToString("0.00", CultureInfo.InvariantCulture));
                sql.Append(", " + r.GetFact(FactType.VOLUME_SALES).ToString("0.00", CultureInfo.InvariantCulture));
                sql.Append(", " + r.GetFact(FactType.UNIT_SALES).ToString("0.00", CultureInfo.InvariantCulture));
                sql.Append(", " + r.GetFact(FactType.VOLUME_ANY_PROMOTION).ToString("0.00", CultureInfo.InvariantCulture));
                sql.Append(", " + r.GetFact(FactType.STORES_SELLING).ToString("0.00", CultureInfo.InvariantCulture));
                sql.Append(", " + r.GetFact(FactType.WEIGHTED_DISTRIBUTION).ToString("0.00", CultureInfo.InvariantCulture));
                sql.Append(", '" + _loadDateTime + "'), ");

                // Hebben we genoeg insert queries
                // Of is dit de laatste?
                if (sql.Length > 20000 || counter == _salesFacts.Count - 1)
                {
                    // Ja: Maak een command en voer de query uit
                    MySqlCommand cmdInsert = new MySqlCommand(sql.ToString(0, sql.Length - 2), _conn);
                    cmdInsert.ExecuteNonQuery();

                    // Maak de string builder leeg
                    // en start met een nieuwe insert query
                    sql.Clear();
                    sql.Append(sqlInsertFactsStart);
                }

                counter++;
            }

            System.Diagnostics.Debug.WriteLine("Total: " + _salesFacts.Count);
        }
    }
}
